package no.ffi.gismoidm.pubsub;

import java.security.PublicKey;
import java.util.HashSet;
import java.util.Iterator;
import no.ffi.gismoidm.idp.Config;
import no.ffi.gismoidm.utils.ClientProxy;
import no.ffi.gismoidm.utils.IdentityStatement;
import no.ffi.gismoidm.utils.GismoIdmException;
import no.ffi.tpm.crypto.CryptoOperations;
import no.ffi.tpm.crypto.CryptoOperationsFactory;
import no.fongen.os.FIFO;

/**
 * Keeper of local credentials like IdentityStatement, trust anchor's 
 * public key, and private key
 * @author anders
 */
public class MyCredentials {
    
    private static MyCredentials instance=null;
    private volatile IdentityStatement myIS;
    private String myDistinguishedName;
    private PublicKey trustAnchorPublicKey;
    private HashSet isDist;
    private CryptoOperations myCrOp;
    
    final static int refreshWhen=10; // Renew IDes 10 min before expiration
    
    public synchronized static MyCredentials getInstance() {
        if (instance == null)
            instance = new MyCredentials();
        return instance;
    }
            
    private MyCredentials() {
        myCrOp = CryptoOperationsFactory.getInstance(CryptoOperationsFactory.JCE);
        myDistinguishedName = myCrOp.getMyDistinguishedName();
        isDist = new HashSet();

        // Load trust anchor
        trustAnchorPublicKey = myCrOp.getTrustAnchor();

        myIS = null;
        while (myIS == null) {
            try {
                myIS = renewIdentityStatement();
            } catch (Exception e ) {
                System.out.println("Exception during IS retrieval: "+ e.getMessage());
                System.out.println("New attempt in 1 min");
                try { Thread.sleep(60000); } catch (Exception ex) {}
            } 
        }


        new ISdistributor(); // Start watch thread
            
    }
    
    private IdentityStatement renewIdentityStatement() throws GismoIdmException {
        String idpURL = Config.getProperty("LOCAL_IDP_URL");
        // TODO prepare for cross-COI ISes also
        return ClientProxy.getIdentityStatement(myDistinguishedName, idpURL, myCrOp);
    }
    
    // Return the most recently issued IS. Note that myIS might have
    // expired, in the case where the IdP is out of service. In that
    // case a null is returned.
    public IdentityStatement getIdentityStatement() {
        if (myIS==null || myIS.identityStatementExpired(0)) return null;
        return myIS;
    }
    
    public String getSubjectId() {
        return myIS.getSubjectName();
    }
    
    public PublicKey getTrustAnchor() { return trustAnchorPublicKey; }
    
    public CryptoOperations getCryptoOperator() { return myCrOp; }
    
    public synchronized void addToIsDistribution(FIFO outwardList) {
        isDist.add(outwardList);
    }
    
    public synchronized void removeFromDistributionList(FIFO outwardList) {
        isDist.remove(outwardList);
    }
    
    public synchronized void distributeIS(IdentityStatement is) {
        Iterator<FIFO> it = isDist.iterator();
        while (it.hasNext()) {
            FIFO x = it.next();
            x.nonblockput(is);
        }
    }   
    
    // Make sure that the Identity statement is updated and dissemminated
    class ISdistributor implements Runnable {
        ISdistributor() {
            Thread t = new Thread(this);
            t.setDaemon(true);
            t.start(); 
        }
        public void run() {
            // Thread for IS renewal. Sleep until "refreshWhen" minutes before expiration
            long sleepFor;
            if (myIS ==null ) sleepFor = 1;
            else sleepFor = myIS.getNotAfter().getTime()-System.currentTimeMillis()-(refreshWhen*60000);
           
            if (sleepFor<1) sleepFor=1; // Avoid negative value if the IS is oldish
            while (true) {
                try { 
                    System.out.println("IS refresh thread sleep for " + sleepFor/1000 + " seconds");
                    Thread.sleep(sleepFor);
                    IdentityStatement xIS = renewIdentityStatement();
                    // encrypt it, send it
                    if (xIS != null) {
                        myIS = xIS;
                        // Send the new IS to the peer
                        sleepFor = myIS.getNotAfter().getTime()-System.currentTimeMillis()-(refreshWhen*60000);
                        if (sleepFor<1) sleepFor=1;
                        System.out.println(myIS.getNotAfter().getTime());
                        System.out.println(System.currentTimeMillis());
                        distributeIS(myIS);
                    } else {// Not sure if null is ever returned though
                        sleepFor = 60000; // New attempt in 1 min
                    }
                } catch (Exception e) {
                    e.printStackTrace(); 
                    sleepFor=60000; // E.g. "connection refused" try again in 1 min
                }
            }
        }
    }
}
